<?php

class DBFactory
{
    static private $instances = array();

    static function getInstances()
    {
        return self::$instances;
    }

    /**
     * @static
     * @param string $db
     * @return OciPDO
     */
    static function getInstance($db = DEFAULT_DB_NAME)
    {
        if (! isset(self::$instances[$db]))
        {
            global $config_arr;
            $config = $config_arr;

            if (isset($config['db'][$db])){
                $config_db = $config['db'][$db];
            }else{
                $config_db = false;
            }

            if ($config_db)
            {
                try
                {
                    if ($config_db['dbtype'] == 'mysql')
                    {
                        self::$instances[$db] = new MysqlPDO('mysql:host=' . $config_db['dbhost'] . ';dbname=' . $config_db['dbname'], $config_db['dbuser'], $config_db['dbpswd'], array(
                            PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8"));
                    }
                    else if ($config_db['dbtype'] == 'oci')
                    {
                        self::$instances[$db] = new OciPDO('oci:dbname='.$config_db['dbname'],$config_db['dbuser'],$config_db['dbpswd']);
                    }
                    else if($config_db['dbtype'] == 'oracle')	//fix for oracle clob
                    {
                        self::$instances[$db] = new Oci($config_db['dbname'],$config_db['dbuser'],$config_db['dbpswd']);
                    }

                    return self::$instances[$db];
                }
                catch(PDOException $e)
                {
                    p($e->getMessage());
                }
            }
            else
            {
                l('config db error:' . $db . ':' . array_keys($config['db']), 'config_db');
                // need to redirect a friendly page
                exit;
            }
        }
        else
            return self::$instances[$db];
    }
}


class OciPDO extends PDO
{
    private $log = array();
    private $connect_time = 0;

    private function doLog($sql, $time, $params = array(), $error = null)
    {
        if (DEBUG == false)
            return;
        $traces = array();
        foreach (array_slice(debug_backtrace(), 2) as $trace)
        {
            $traces[] = array(
                'file'  =>  $trace['file'],
                'line'  =>  $trace['line'],
                'func'  =>  $trace['function'],
            );
        }

        $this->log[] = array(
            'sql'   =>  $sql,
            'params'=>  $params,
            'time'  =>  $time,
            'trace' =>  $traces,
            'error' =>  $error,
        );
    }

    public function getLog()
    {
        return $this->log;
    }

    public function getConnTime()
    {
        return $this->connect_time;
    }

    function __construct($dsn, $username = '', $password = '', $driver_options = array())
    {
        try
        {
            $start_time = microtime(true);
            parent::__construct($dsn, $username, $password, array(PDO::ATTR_CASE=>PDO::CASE_LOWER) + $driver_options);
            $end_time = microtime(true);

            $connect_time = round($end_time - $start_time, 3);

            $this->connect_time = $connect_time;
        }
        catch(PDOException $e)
        {
//            d(func_get_args(), false);
            l(array_merge(array('dsn'=>$dsn), (array)$e->getMessage()), 'db_connect');
        }
    }

    public function getLastInsertID($sequence_name)
    {
        $sql = "select " . $sequence_name . ".currval from dual";
        return $this->getOne($sql);
    }

    public function getNextSeqID($sequence_name)
    {
        $sql = "select " . $sequence_name . ".nextval from dual";
        return $this->getOne($sql);
    }

    public function selectLimit($sql, $limit, $limit_from = 0, $params = array(), $driver_options = array())
    {
        $limit_to = $limit_from + $limit;
        $sql = "SELECT * FROM ( SELECT a.*, rownum r FROM ( " . $sql . " ) a WHERE rownum <= :limit_to ) b WHERE r > :limit_from";
        return $this->getAll($sql, array_merge($params, array('limit_to'=>$limit_to, 'limit_from'=>$limit_from)), $driver_options);
    }

    public function getAll($sql, $params = array(), $driver_options = array(), $fetch_style = PDO::FETCH_ASSOC)
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $all = array();
        while ($row = $stmt->fetch($fetch_style))
        {
            if ($row)
            {
                foreach ($row as $col => $val)
                {
                    if (is_resource($val))
                        $row[$col] = stream_get_contents($val);
                }
                $all[] = $row;
            }
        }
        return $all;
    }

    public function getOneAll($sql, $params = array(), $driver_options = array(), $column_number = 0)
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $ones = array();
        while ($one = $stmt->fetch(PDO::FETCH_NUM))
        {
            $column_value = $one[$column_number];
            if (is_resource($column_value))
                $ones[] = stream_get_contents($column_value);
            else
                $ones[] = $column_value;
        }

        return $ones;
    }

    public function getRow($sql, $params = array(), $driver_options = array(), $fetch_style = PDO::FETCH_ASSOC)
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $row = $stmt->fetch($fetch_style);

        if ($row)
        {
            foreach ($row as $col => $val)
            {
                if (is_resource($val))
                    $row[$col] = stream_get_contents($val);
            }
        }

        return $row;
    }

    public function getOne($sql, $params = array(), $driver_options = array(), $column_number = 0)
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $one = $stmt->fetchColumn($column_number);
        if (is_resource($one))
            return stream_get_contents($one);
        else
            return $one;
    }

    public function execute($sql, $params = array(), $driver_options = array(), $transaction = false)
    {
        $start_time = microtime(true);
        $stmt = $this->prepare($sql, $driver_options);

        if ($stmt)
        {
            foreach ($params as $k => &$param)
            {
                $stmt->bindParam($k, $param, PDO::PARAM_STR, strlen($param));
            }

            if ($transaction)
                $this->beginTransaction();

            $ret = $stmt->execute();

            if ($transaction)
            {
                if ($ret)
                {
                    $this->commit();
                }
                else
                {
                    $this->rollBack();
                }
            }
        }
        else
        {
            $ret = false;
        }

        $end_time = microtime(true);
        $exec_time = $end_time - $start_time;

        if (! defined('LOG_DB_LONGTIME'))
            define('LOG_DB_LONGTIME', false);

        if (LOG_DB_LONGTIME && $exec_time > LOG_DB_LONGTIME)
        {
            l(array('sql'=>$sql, 'params'=>$params, 'time'=>$exec_time), 'db_longtime');
        }

        $error_msg = null;
        if (! $ret)
        {
            $error = $stmt ? $stmt->errorInfo() : $this->errorInfo();
            if (isset($error[2]) && $error[2])
            {
                $error_msg = $error[2];
                l(array('sql'=>$sql, 'params'=>$params, 'error'=>$error_msg), 'runtime');
            }
        }
        if (defined('RUNNING_MODE') && RUNNING_MODE == 'dev')
        {
            $this->doLog($sql, $exec_time, $params, $error_msg);
        }
        return $stmt;
    }
}

class Oci
{
    public function __construct($db,$user,$password)
    {
        $this->dbLink = ocilogon($user, $password, $db);
        if(!$this->dbLink)
        {
            $e = ocierror();
            die(htmlentities($e['message']));
        }
    }

    public function getAll($sql,$params = array(),$bind = false)
    {
        unset($this->stmt);
        $this->stmt = ociparse($this->dbLink,$sql);

        if (!empty($params) && is_array($params))
        {
            foreach($params as $k=>$v)
            {
                if($bind)
                {
                    ocibindbyname($this->stmt, ":".$k, $v, 4000);
                }
                else
                {
                    ocibindbyname($this->stmt, ":".$k, $v, -1);
                }

                unset($v);
            }
        }

        if(ociexecute($this->stmt))
        {
            $result = array();
            while(ocifetchinto($this->stmt, $results, OCI_ASSOC+OCI_RETURN_LOBS+OCI_RETURN_NULLS) )
            {
                $result[] = @array_change_key_case($results);
            }

            return $result;
        }
        else
        {
            return false;
        }

    }

    public function getRow($sql,$params = array(),$bind = false)
    {
        unset($this->stmt);
        $this->stmt = ociparse($this->dbLink,$sql);

        if (!empty($params) && is_array($params))
        {
            foreach($params as $k=>$v)
            {
                if($bind)
                {
                    ocibindbyname($this->stmt, ":".$k, $v, 4000);
                }
                else
                {
                    ocibindbyname($this->stmt, ":".$k, $v, -1);
                }
                unset($v);
            }
        }
        if(ociexecute($this->stmt))
        {
            ocifetchinto($this->stmt, $results, OCI_ASSOC+OCI_RETURN_LOBS);
            $return = @array_change_key_case($results);
            return $return;
        }
        else
        {
            return false;
        }
    }

    public function execute($sql, $params = array (), $config = array('auto_commit'=>true,'return_type'=>1,'var_size'=>0))
    {
        $this->stmt = ociparse($this->dbLink,$sql);

        if (!empty($params) && is_array($params))
        {
            foreach($params as $k=>$v)
            {
                if($config['var_size'] == 0)
                {
                    ocibindbyname($this->stmt, ":".$k, $params[$k]);

                }
                else
                {
                    ocibindbyname($this->stmt, ":".$k, $params[$k], (int)$config['var_size']);
                }

            }
        }

        if($config['auto_commit'])
        {
            $success = OCIExecute($this->stmt);
        }
        else
        {
            $success = OCIExecute($this->stmt,OCI_DEFAULT);
        }
        if($success)
        {
            if($config['return_type'] == 1)
            {
                return ocirowcount($this->stmt);
            }
            else
            {
                return true;
            }
        }
        else
        {
            return false;
        }
    }

}

class SQLitePDO extends PDO
{
    private $log = array();
    private $connect_time = 0;

    private function doLog($sql, $time, $params = array(), $error = null)
    {
        if (DEBUG == false)
            return;
        $traces = array();
        foreach (array_slice(debug_backtrace(), 2) as $trace)
        {
            $traces[] = array(
                'file'  =>  $trace['file'],
                'line'  =>  $trace['line'],
                'func'  =>  $trace['function'],
            );
        }

        $this->log[] = array(
            'sql'   =>  $sql,
            'params'=>  $params,
            'time'  =>  $time,
            'trace' =>  $traces,
            'error' =>  $error,
        );
    }

    public function getLog()
    {
        return $this->log;
    }

    public function getConnTime()
    {
        return $this->connect_time;
    }

    function __construct($dsn, $driver_options = array())
    {
        try
        {
            $start_time = microtime(true);
            parent::__construct($dsn, null, null, array(PDO::ATTR_CASE=>PDO::CASE_LOWER) + $driver_options);
            $end_time = microtime(true);

            $connect_time = round($end_time - $start_time, 3);

            $this->connect_time = $connect_time;
        }
        catch(PDOException $e)
        {
            p(array_merge(array('dsn'=>$dsn), (array)$e->getMessage()), 'db_connect');
        }
    }

    public function selectLimit($sql, $limit, $limit_from = 0, $params = array(), $driver_options = array())
    {
        $sql .= " limit :limit_from,:limit";
        return $this->getAll($sql, array_merge($params, array('limit'=>$limit, 'limit_from'=>$limit_from)), $driver_options);
    }

    public function getAll($sql, $params = array(), $driver_options = array(), $fetch_style = PDO::FETCH_ASSOC)
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $all = $stmt->fetchAll($fetch_style);
        return $all;
    }

    public function getOneAll($sql, $params = array() , $column_number = 0, $driver_options = array())
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $all = array();
        while ($row = $stmt->fetch(PDO::FETCH_NUM))
        {
            $all[] = $row[$column_number];
        }
        return $all;
    }

    public function getRow($sql, $params = array(), $driver_options = array(), $fetch_style = PDO::FETCH_ASSOC)
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $row = $stmt->fetch($fetch_style);
        return $row;
    }

    public function getOne($sql, $params = array(), $driver_options = array(), $column_number = 0)
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $one = $stmt->fetchColumn($column_number);
        return $one;
    }

    public function execute($sql, $params = array(), $driver_options = array(), $transaction = false)
    {
        $start_time = microtime(true);
        $stmt = $this->prepare($sql, $driver_options);

        if ($stmt)
        {
            foreach ($params as $k => &$param)
            {
                $stmt->bindParam($k, $param, PDO::PARAM_STR, strlen($param));
            }

            if ($transaction)
                $this->beginTransaction();

            $ret = $stmt->execute();

            if ($transaction)
            {
                if ($ret)
                {
                    $this->commit();
                }
                else
                {
                    $this->rollBack();
                }
            }
        }
        else
        {
            $ret = false;
        }

        $end_time = microtime(true);
        $exec_time = $end_time - $start_time;

        if (! defined('LOG_DB_LONGTIME'))
            define('LOG_DB_LONGTIME', false);

        if (LOG_DB_LONGTIME && $exec_time > LOG_DB_LONGTIME)
        {
            p(array('sql'=>$sql, 'params'=>$params, 'time'=>$exec_time), 'db_longtime');
        }

        $error_msg = null;
        if (! $ret)
        {
            $error = $stmt ? $stmt->errorInfo() : $this->errorInfo();
            if (isset($error[2]) && $error[2])
            {
                $error_msg = $error[2];
                p(array('sql'=>$sql, 'params'=>$params, 'error'=>$error_msg), 'db_runtime');
            }
        }
        if (defined('RUNNING_MODE') && RUNNING_MODE == 'dev')
        {
            $this->doLog($sql, $exec_time, $params, $error_msg);
        }
        return $stmt;
    }
}

class MysqlPDO extends PDO
{
    private $log = array();
    private $connect_time = 0;

    private function doLog($sql, $time, $params = array(), $error = null)
    {
        if (DEBUG == false)
            return;
        $traces = array();
        foreach (array_slice(debug_backtrace(), 2) as $trace)
        {
            $traces[] = array(
                'file'  =>  $trace['file'],
                'line'  =>  $trace['line'],
                'func'  =>  $trace['function'],
            );
        }

        $this->log[] = array(
            'sql'   =>  $sql,
            'params'=>  $params,
            'time'  =>  $time,
            'trace' =>  $traces,
            'error' =>  $error,
        );
    }

    public function getLog()
    {
        return $this->log;
    }

    public function getConnTime()
    {
        return $this->connect_time;
    }

    function __construct($dsn, $username, $password, $driver_options = array())
    {
        try
        {
            $start_time = microtime(true);
            parent::__construct($dsn, $username, $password, array(PDO::ATTR_CASE=>PDO::CASE_LOWER) + $driver_options);
            $end_time = microtime(true);

            $connect_time = round($end_time - $start_time, 3);

            $this->connect_time = $connect_time;
        }
        catch(PDOException $e)
        {
            p(array_merge(array('dsn'=>$dsn), (array)$e->getMessage()), 'db_connect');
        }
    }

    public function selectLimit($sql, $limit, $limit_from = 0, $params = array(), $driver_options = array())
    {
        $sql .= " limit :limit_from,:limit";
        return $this->getAll($sql, array_merge($params, array('limit'=>$limit, 'limit_from'=>$limit_from)), $driver_options);
    }

    public function getAll($sql, $params = array(), $driver_options = array(), $fetch_style = PDO::FETCH_ASSOC)
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $all = $stmt->fetchAll($fetch_style);
        return $all;
    }

    public function getOneAll($sql, $params = array() , $column_number = 0, $driver_options = array())
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $all = array();
        while ($row = $stmt->fetch(PDO::FETCH_NUM))
        {
            $all[] = $row[$column_number];
        }
        return $all;
    }

    public function getRow($sql, $params = array(), $driver_options = array(), $fetch_style = PDO::FETCH_ASSOC)
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $row = $stmt->fetch($fetch_style);
        return $row;
    }

    public function getOne($sql, $params = array(), $driver_options = array(), $column_number = 0)
    {
        $stmt = $this->execute($sql, $params, $driver_options);
        $one = $stmt->fetchColumn($column_number);
        return $one;
    }

    public function execute($sql, $params = array(), $driver_options = array(), $transaction = false)
    {
        $start_time = microtime(true);
        $stmt = $this->prepare($sql, $driver_options);

        if ($stmt)
        {
            foreach ($params as $k => &$param)
            {
                $stmt->bindParam($k, $param, PDO::PARAM_STR, strlen($param));
            }

            if ($transaction)
                $this->beginTransaction();

            $ret = $stmt->execute();

            if ($transaction)
            {
                if ($ret)
                {
                    $this->commit();
                }
                else
                {
                    $this->rollBack();
                }
            }
        }
        else
        {
            $ret = false;
        }

        $end_time = microtime(true);
        $exec_time = $end_time - $start_time;

        if (! defined('LOG_DB_LONGTIME'))
            define('LOG_DB_LONGTIME', false);

        if (LOG_DB_LONGTIME && $exec_time > LOG_DB_LONGTIME)
        {
            p(array('sql'=>$sql, 'params'=>$params, 'time'=>$exec_time), 'db_longtime');
        }

        $error_msg = null;
        if (! $ret)
        {
            $error = $stmt ? $stmt->errorInfo() : $this->errorInfo();
            if (isset($error[2]) && $error[2])
            {
                $error_msg = $error[2];
                p(array('sql'=>$sql, 'params'=>$params, 'error'=>$error_msg), 'db_runtime');
            }
        }
        if (defined('RUNNING_MODE') && RUNNING_MODE == 'dev')
        {
            $this->doLog($sql, $exec_time, $params, $error_msg);
        }
        return $stmt;
    }
}
?>